#include "log.h"
#include "bytearray.h"
#include "endian.h"

#include <string.h>
#include <sstream>
#include <iostream>
#include <iomanip>
#include <fstream>



namespace qtch{

static Logger::ptr logger = QTCH_LOG_NAME("system");

ByteArray::Node::Node(size_t s)
    :ptr(new char[s])
    ,next(nullptr)
    ,size(s){
}

ByteArray::Node::Node()
    :ptr(nullptr)
    ,next(nullptr)
    ,size(0){
}

ByteArray::Node::~Node(){
    
    if(ptr){
        delete[] ptr;
    }
}

ByteArray::ByteArray(size_t base_size)
    :m_baseSize(base_size)
    ,m_readPosition(0)
    ,m_writePosition(0)
    ,m_capacity(base_size)
    ,m_endian(QTCH_BIG_ENDIAN)
    ,m_root(new Node(base_size))
    ,m_readCur(m_root)
    ,m_writeCur(m_root){
}

ByteArray::~ByteArray(){
    // QTCH_LOG_DEBUG(logger) << "~Node capacity=" << m_capacity
    //                     << " m_readPosition=" << m_readPosition
    //                     << " m_writePosition=" << m_writePosition
    //                     << " m_baseSize=" << m_baseSize;
    Node* temp = m_root;
    while(temp){
        m_readCur = temp;
        temp = temp->next;
        delete m_readCur;
    }
}

void ByteArray::writeFint8(int8_t value){
    write(&value,sizeof(value));
}

void ByteArray::writeFuint8(uint8_t value){
    write(&value,sizeof(value));
}

void ByteArray::writeFint16(int16_t value){
    if(m_endian != QTCH_BYTE_ORDER){
        value = byteswap(value);
    }
    write(&value,sizeof(value));
}

void ByteArray::writeFuint16(uint16_t value){
    if(m_endian != QTCH_BYTE_ORDER){
        value = byteswap(value);
    }
    write(&value,sizeof(value));
}

void ByteArray::writeFint32(int32_t value){
    if(m_endian != QTCH_BYTE_ORDER){
        value = byteswap(value);
    }
    write(&value,sizeof(value));
}

void ByteArray::writeFuint32(uint32_t value){
    if(m_endian != QTCH_BYTE_ORDER){
        value = byteswap(value);
    }
    write(&value,sizeof(value));
}

void ByteArray::writeFint64(int64_t value){
    if(m_endian != QTCH_BYTE_ORDER){
        value = byteswap(value);
    }
    write(&value,sizeof(value));
}

void ByteArray::writeFuint64(uint64_t value){
    if(m_endian != QTCH_BYTE_ORDER){
        value = byteswap(value);
    }
    write(&value,sizeof(value));
}

static uint32_t EncodeZigzag32(const int32_t& v){
    return (uint32_t)((v>>31) ^ (v<<1));
}

static uint64_t EncodeZigzag64(const int64_t& v){
    return (uint64_t)((v>>63) ^ (v<<1)); 
}
static int32_t DecodeZigzag32(const uint32_t& v) {
    return (int32_t)((v >> 1) ^ -(v & 1));
}

static int64_t DecodeZigzag64(const uint64_t& v) {
    return (int64_t)((v >> 1) ^ -(v & 1));
}

void ByteArray::writeInt32(int32_t value){
    writeUint32(EncodeZigzag32(value));
}

void ByteArray::writeUint32(uint32_t value){
    uint8_t tmp[5];
    uint8_t i = 0;
    while(value >= 0x80){
        tmp[i++] = (value & 0x7f) | 0x80;
        value >>= 7;
    }
    tmp[i++] = value;
    write(tmp,i);
}

void ByteArray::writeInt64(int64_t value){
    writeUint64(EncodeZigzag64(value));
}

void ByteArray::writeUint64(uint64_t value){
    uint8_t tmp[10];
    uint8_t i = 0;
    while(value >= 0x80){
        tmp[i++] = (value & 0x7f) | 0x80;
        value >>= 7;
    }
    tmp[i++] = value;
    write(tmp,i);
}

void ByteArray::writeFloat(float value){
    uint32_t tmp;
    memcpy(&tmp,&value,sizeof(tmp));
    writeUint32(tmp);
}

void ByteArray::writeDouble(double value){
    uint64_t tmp;
    memcpy(&tmp,&value,sizeof(tmp));
    writeUint64(tmp);
}

void ByteArray::writeStringF16(const std::string& value){
    writeFuint16(value.size());
    write(value.c_str(),value.size());

}

void ByteArray::writeStringF32(const std::string& value){
    writeFuint32(value.size());
    write(value.c_str(),value.size());
}

void ByteArray::writeStringF64(const std::string& value){
    writeFuint64(value.size());
    write(value.c_str(),value.size());
}

void ByteArray::writeStringVint(const std::string& value){
    writeUint64(value.size());
    write(value.c_str(),value.size());
}

void ByteArray::writeStringWithoutLength(const std::string& value){
    write(value.c_str(),value.size());
}

#define XX(type) \
    type v; \
    read(&v,sizeof(v)); \
    if(m_endian == QTCH_BYTE_ORDER) { \
        return v; \
    } else { \
        return byteswap(v); \
    }

int8_t ByteArray::readFint8(){
    int8_t v;
    read(&v,sizeof(v));
    return v;
}

uint8_t ByteArray::readFuint8(){
    uint8_t v;
    read(&v,sizeof(v));
    return v;
}

int16_t ByteArray::readFint16(){
    XX(int16_t);
}

uint16_t ByteArray::readFuint16(){
    XX(uint16_t);
}

int32_t ByteArray::readFint32(){
    XX(int32_t);
}

uint32_t ByteArray::readFuint32(){
    XX(uint32_t);
}

int64_t ByteArray::readFint64(){
    XX(int64_t);
}

uint64_t ByteArray::readFuint64(){
    XX(uint64_t);
}

#undef XX

int32_t ByteArray::readInt32(){
    return DecodeZigzag32(readUint32());
}

uint32_t ByteArray::readUint32(){
    uint32_t result = 0;
    for(int i = 0; i < 32; i += 7){
        uint8_t tmp = readFuint8();
        if(tmp < 0x80){
            result |= ((uint32_t)tmp) << i;
            break;
        }else{
            result |= (((uint32_t)(tmp & 0x7f)) << i);
        }
    }
    return result;
}

int64_t ByteArray::readInt64(){
    return DecodeZigzag64(readUint64());
}

uint64_t ByteArray::readUint64(){
    uint64_t result = 0;
    for(int i = 0; i < 64; i += 7){
        uint8_t tmp = readFuint8();
        if(tmp < 0x80){
            result |= ((uint64_t)tmp) << i;
            break;
        }else{
            result |= (((uint64_t)(tmp & 0x7f)) << i);
        }
    }
    return result;
}

float ByteArray::readFloat(){
    uint32_t v = readUint32();
    float result;
    memcpy(&result,&v,sizeof(result));
    return result;
}

double ByteArray::readDouble(){
    uint64_t v = readUint32();
    double result;
    memcpy(&result,&v,sizeof(result));
    return result;
}

std::string ByteArray::readStringF16(){
    uint16_t len =readFuint16();
    std::string buf;
    buf.resize(len);
    read(&buf[0],len);
    return buf;
}

std::string ByteArray::readStringF32(){
    uint32_t len =readFuint32();
    std::string buf;
    buf.resize(len);
    read(&buf[0],len);
    return buf;
}

std::string ByteArray::readStringF64(){
    uint64_t len =readFuint64();
    std::string buf;
    buf.resize(len);
    read(&buf[0],len);
    return buf;
}

std::string ByteArray::readStringVint(){
    uint64_t len =readUint64();
    std::string buf;
    buf.resize(len);
    read(&buf[0],len);
    return buf;
}

void ByteArray::clear(){
    m_readPosition = 0;
    m_writePosition = 0;
    m_capacity = m_baseSize;
    Node * tmp = m_root->next;
    while(tmp){
        m_readCur = tmp;
        tmp = tmp->next;
        delete m_readCur;
    }
    m_readCur = m_root;
    m_writeCur = m_root;
    m_root->next = nullptr;
}

void ByteArray::write(const void* buffer, size_t size){
    if(size == 0){
        return;
    }
    addCapacity(size);

    size_t npos = m_writePosition % m_baseSize;
    size_t ncap = m_writeCur->size - npos;
    size_t bpos = 0;

    while(size>0){
        if(ncap >= size){
            memcpy(m_writeCur->ptr+npos,(const char*)buffer+bpos,size);
            if(m_writeCur->size == (npos + size)){
                m_writeCur = m_writeCur->next;
            }
            m_writePosition += size;
            bpos += size;
            size = 0;
        }else{
            memcpy(m_writeCur->ptr+npos,(const char*)buffer+bpos,ncap);
            m_writePosition += ncap;
            bpos += ncap;
            size -= ncap;
            m_writeCur = m_writeCur->next;
            ncap = m_writeCur->size;
            npos = 0;
        }
    }


}

void ByteArray::read(void* buf, size_t size){
    if(size > getReadSize()){
        throw std::out_of_range("not enough len");
    }
    size_t npos = m_readPosition % m_baseSize;
    size_t ncap = m_readCur->size - npos;
    size_t bpos = 0;
    while(size>0){
        if(ncap >= size){
            memcpy((char *)buf + bpos,m_readCur->ptr+npos,size);
            if(ncap == size){
                m_readCur = m_readCur->next;
            }
            m_readPosition += size;
            bpos += size;
            size = 0;
        } else {
            memcpy((char *)buf + bpos,m_readCur->ptr+npos,ncap);
            bpos += ncap;
            npos = 0;
            size -= ncap;
            m_readPosition += ncap;
            m_readCur = m_readCur->next;
            ncap = m_readCur->size;
        }
    }
}

void ByteArray::read(void* buf, size_t size, size_t position) const{
    if(size > getReadSize()){
        throw std::out_of_range("not enough len");
    }
    size_t npos = position % m_baseSize;
    size_t ncap = m_readCur->size - npos;
    size_t bpos = 0;
    Node * cur = m_readCur;
    while(size>0){
        if(ncap >= size){
            memcpy((char *)buf + bpos,cur->ptr+npos,size);
            if(ncap == size){
                cur = cur->next;
            }
            position += size;
            bpos += size;
            size = 0;
        } else {
            memcpy((char *)buf + bpos,cur->ptr+npos,ncap);
            bpos += ncap;
            npos = 0;
            size -= ncap;
            position += ncap;
            cur = cur->next;
            ncap = cur->size;
        }
    }
}

void ByteArray::setReadPosition(size_t v){
    if(v > m_writePosition){
        v = m_writePosition;
    }
    m_readPosition = v;
    m_readCur = m_root;
    while(v >= m_readCur->size){
        v -= m_readCur->size;
        m_readCur = m_readCur->next;
    }

}

void ByteArray::setWritePosition(size_t v){
    if(v > m_capacity){
        std::out_of_range("set write position out of range");
    }
    m_writePosition = v;
    m_writeCur = m_root;
    while(v >= m_writeCur->size){
        v -= m_writeCur->size;
        m_writeCur = m_writeCur->next;
    }
}

bool ByteArray::readFromFile(const std::string& name){
    std::ifstream ifs;
    ifs.open(name, std::ios::binary);
    if(!ifs){
        QTCH_LOG_ERROR(logger) << "readFromFile name=" << name
            << " error, errno=" << errno << " errstr=" << strerror(errno);
        return false;
    }
    std::shared_ptr<char> buff(new char[m_baseSize], [](char * ptr){ delete [] ptr;});
    while(!ifs.eof()){
        ifs.read(buff.get(),m_baseSize);
        write(buff.get(),ifs.gcount());
    }
    return true;
}

bool ByteArray::writeToFile(const std::string& name){
    std::ofstream ofs;
    ofs.open(name, std::ios::binary | std::ios::trunc);
    if(!ofs){
        QTCH_LOG_ERROR(logger) << "writeToFile name=" << name
            << " error, errno=" << errno << " errstr=" << strerror(errno);
        return false;
    }
    std::shared_ptr<char> buff(new char[m_baseSize], [](char * ptr){ delete [] ptr;});
    size_t read_size = getReadSize();
    Node* node = m_readCur;
    size_t pos = m_readPosition;
    while(read_size>0){
        int diff = pos % m_baseSize;
        int len = (read_size > m_baseSize ? m_baseSize -diff: read_size);
        ofs.write(node->ptr + diff, len);
        node = node->next;
        pos += len;
        read_size -= len;
    }
    return true;
}

bool ByteArray::isLittleEndian() const{
    return m_endian == QTCH_LITTLE_ENDIAN;
}

void ByteArray::setIsLittleEndian(bool v){
    if(v){
        m_endian = QTCH_LITTLE_ENDIAN;
    } else {
        m_endian = QTCH_BIG_ENDIAN;
    }
}

uint64_t ByteArray::getReadBuffer(std::vector<iovec>& buffer, uint64_t len) const{
    len = len > getReadSize() ? getReadSize() : len;
    if(len==0){
        return 0;
    }
    uint64_t size = len;
    size_t npos = m_readPosition % m_baseSize;
    size_t ncap = m_readCur->size - npos;
    struct iovec iov;
    Node* cur = m_readCur;
    while(len>0){
        if(ncap >= len){
            iov.iov_base = cur->ptr + npos;
            iov.iov_len = len;
            len = 0;
        } else {
            iov.iov_base = cur->ptr + npos;
            iov.iov_len = ncap;
            len -= ncap;
            npos = 0;
            cur = cur->next;
            ncap = cur->size;
        }
        buffer.push_back(iov);
    }
    return size;
}

uint64_t ByteArray::getReadBuffer(std::vector<iovec>& buffer, uint64_t len, uint64_t position) const{
    len = len > getReadSize() ? getReadSize() : len;
    if(len==0){
        return 0;
    }
    uint64_t size = len;
    size_t npos = position % m_baseSize;
    size_t count = position / m_baseSize;
    struct iovec iov;
    Node* cur = m_root;
    while(count>0){
        cur = cur->next;
        count--;
    }
    size_t ncap = cur->size - npos;
    while(len>0){
        if(ncap >= len){
            iov.iov_base = cur->ptr + npos;
            iov.iov_len = len;
            len = 0;
        } else {
            iov.iov_base = cur->ptr + npos;
            iov.iov_len = ncap;
            len -= ncap;
            npos = 0;
            cur = cur->next;
            ncap = cur->size;
        }
        buffer.push_back(iov);
    }
    return size;
}   

uint64_t ByteArray::getWriteBuffer(std::vector<iovec>& buffer, uint64_t len){
    if(len==0){
        return 0;
    }
    addCapacity(len);
    uint64_t size = len;
    size_t npos = m_writePosition % m_baseSize;
    size_t ncap = m_writeCur->size - npos;
    struct iovec iov;
    Node* cur = m_writeCur;
    while(len>0){
        if(ncap >= len){
            iov.iov_base = cur->ptr + npos;
            iov.iov_len = len;
            len = 0;
        } else {
            iov.iov_base = cur->ptr + npos;
            iov.iov_len = ncap;
            len -= ncap;
            npos = 0;
            cur = cur->next;
            ncap = cur->size;
        }
        buffer.push_back(iov);
    }
    return size;
}

void ByteArray::addCapacity(size_t size){
    if(size == 0){
        return;
    }
    // size +=1;
    size_t old_cap = getCapacity();
    if(old_cap > size){
        return;
    }
    int addCap = m_writePosition + size - m_capacity;
    Node* node = m_writeCur;
    while(node->next){
        node=node->next;
    }
    while(addCap>=0){
        node->next = new Node(m_baseSize);
        m_capacity += m_baseSize;
        node = node->next;
        addCap -= m_baseSize;
    }
    
}

std::string ByteArray::toString() const{
    size_t len = getReadSize();
    std::string buf;
    buf.resize(len);
    if(buf.empty()){
        return buf;
    }
    read(&buf[0],buf.size(), m_readPosition);
    return buf;
}

std::string ByteArray::toHexString() const{
    std::string str = toString();
    std::stringstream ss;
    for(size_t i = 0; i < str.size(); ++i){
        if(i>0 && i%32 == 0){
            ss << "\n";
        }
        ss << std::setw(2) << std::setfill('0') << std::hex
            <<(int)(uint8_t)str[i] << " ";

    }
    return ss.str();
}

void ByteArray::clearUnused(){
    int deleteNodeNum = 0;
    Node * tmp = m_root;
    while(tmp != m_readCur && tmp != m_writeCur){
        Node * t = tmp;
        tmp = tmp->next;
        ++deleteNodeNum;
        delete t;
    }
    m_root = tmp;
    size_t deleteSize = deleteNodeNum * m_baseSize;
    m_capacity -= deleteSize;
    m_readPosition -= deleteSize;
    m_writePosition -= deleteSize;
    
}



}